package LibDL.recommender;

import LibDL.core.nn.Module;
import LibDL.recommender.data.utils.NumUtils;
import com.google.common.collect.BiMap;
import net.librec.common.LibrecException;
import net.librec.conf.Configuration;
import net.librec.data.DataModel;
import net.librec.data.model.TextDataModel;
import net.librec.job.progress.ProgressBar;
import net.librec.math.structure.SequentialAccessSparseMatrix;
import net.librec.recommender.RecommenderContext;
import net.librec.recommender.item.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Deprecated
public abstract class AbstractLibDLRecommender implements Recommender, net.librec.recommender.Recommender {

//    protected String train_root;
//
//    protected String test_root;
//
//    protected boolean data_input_is_split;

    protected boolean use_cuda;

    protected Configuration conf;

    protected DataModel dataModel = null;

    protected Module module = null;

    /**
     * LOG
     */
    protected final Log LOG = LogFactory.getLog(this.getClass());

    /**
     * is ranking or rating
     */
    protected boolean isRanking;

    /**
     * topN
     */
    protected int topN;



    /**
     * RecommenderContext
     */
    protected RecommenderContext context;

    /**
     * early-stop criteria
     */
    protected boolean earlyStop;

    /**
     * verbose
     */
    protected static boolean verbose = true;

    /**
     * objective loss
     */
    protected double loss, lastLoss = 0.0d;

    /**
     * whether to adjust learning rate automatically
     */
    protected boolean isBoldDriver;

    /**
     * decay of learning rate
     */
    protected float decay;

    /**
     * report the training progress
     */
    protected ProgressBar progressBar;

    /**
     * user Mapping Data
     */
    public BiMap<String, Integer> userMappingData;

    /**
     * item Mapping Data
     */
    public BiMap<String, Integer> itemMappingData;

    protected AbstractLibDLRecommender(Builder builder) {
        use_cuda = builder.use_cuda;
//        train_root = builder.train_root;
//        test_root = builder.test_root;
//        data_input_is_split = builder.data_input_is_split;
        conf = new Configuration();
        conf.set("dfs.data.dir", builder.dft_data_dir);
        StringBuilder input = new StringBuilder();
        for (String s : builder.data_input_path) {
            input.append(s);
            input.append(':');
        }
//        conf.set("data.input.path", input.toString());
//        conf.set("data.splitter.trainset.ratio", String.valueOf(builder.data_splitter_ratio));
        conf.set("data.input.path", builder.data_input_path[0]);
        conf.set("data.model.splitter", "testset");
        conf.set("data.testset.path", builder.data_input_path[1]);
        conf.set("data.convert.sep", builder.data_convert_sep);
        conf.set("rec.recommender.ranking.topn", String.valueOf(builder.rec_recommender_ranking_topn));
    }

    @Override
    public void generateDataModel() throws LibrecException {
        dataModel = new TextDataModel(conf);
        dataModel.buildDataModel();
    }

    @Override
    public DataModel getDataModel() {
        return dataModel;
    }

    @Override
    public Module getModule() {
        return this.module;
    }

    public Configuration getConf() {
        return conf;
    }

    @Override
    public abstract void initModule();

    @Override
    public abstract List<Float> predict(int user, @Nullable List<Integer> item_ids);

    @Override
    public abstract void trainModel(boolean verbose);

    @Override
    public RecommendedList recommendRank(List<Integer> user_ids) {
        RecommendedList rl = new RecommendedList(user_ids.size());
        ArrayList<KeyValue<Integer, Double>> row;
        int topn = conf.getInt("rec.recommender.ranking.topn");
        for (Integer user_id : user_ids) {
            row = new ArrayList<>(topn);
            final List<Float> pred = this.predict(user_id, null);
            int[] train = ((SequentialAccessSparseMatrix) dataModel.getTrainDataSet()).row(user_id).getIndices();
            List<Integer> indices = NumUtils.argsort(pred, (o1, o2) -> pred.get(o2).compareTo(pred.get(o1)));
            indices.removeAll(IntStream.of(train).boxed().collect(Collectors.toList()));
            for (int j = 0; j < topn; j++) {
                row.add(new KeyValue<>(indices.get(j), (double) pred.get(indices.get(j))));
            }
            rl.addList(row);
        }
        return rl;
    }
    public List<RecommendedItem> getRecommendedList(RecommendedList recommendedList) {

        if (recommendedList != null && recommendedList.size() > 0) {
            List<RecommendedItem> userItemList = new ArrayList<>();
            Iterator<ContextKeyValueEntry> recommendedEntryIter = recommendedList.iterator();
            if (userMappingData != null && userMappingData.size() > 0 && itemMappingData != null && itemMappingData.size() > 0) {
                BiMap<Integer, String> userMappingInverse = userMappingData.inverse();
                BiMap<Integer, String> itemMappingInverse = itemMappingData.inverse();
                while (recommendedEntryIter.hasNext()) {
                    ContextKeyValueEntry contextKeyValueEntry = recommendedEntryIter.next();
                    if (contextKeyValueEntry != null) {
                        String userId = userMappingInverse.get(contextKeyValueEntry.getContextIdx());
                        String itemId = itemMappingInverse.get(contextKeyValueEntry.getKey());
                        if (StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(itemId)) {
                            userItemList.add(new GenericRecommendedItem(userId, itemId, contextKeyValueEntry.getValue()));
                        }
                    }
                }
                return userItemList;
            }
        }
        return null;
    }
    public static abstract class Builder {

        protected String[] data_input_path;

        protected String dft_data_dir = System.getProperty("user.dir");

        protected double data_splitter_ratio = 0.8;

//        protected String train_root = "";
//
//        protected String test_root = "";
//
//        protected boolean data_input_is_split = false;

        protected String data_convert_sep = " ";

        protected boolean use_cuda = true;

        protected int rec_recommender_ranking_topn = 10;

        protected Builder(String... data_input_path) {
            this.data_input_path = data_input_path;
        }

        public Builder dft_data_dir(String dft_data_dir) {
            this.dft_data_dir = dft_data_dir;
            return this;
        }

        public Builder data_split_rate(double data_splitter_ratio) {
            this.data_splitter_ratio = data_splitter_ratio;
            return this;
        }

        public Builder data_convert_sep(String data_convert_sep) {
            this.data_convert_sep = data_convert_sep;
            return this;
        }

        public Builder use_cuda(boolean use_cuda) {
            this.use_cuda = use_cuda;
            return this;
        }

        public Builder rec_recommender_ranking_topn(int rec_recommender_ranking_topn) {
            this.rec_recommender_ranking_topn = rec_recommender_ranking_topn;
            return this;
        }

        public abstract Recommender build() throws LibrecException;

    }
}
